1use rusqlite::params;
26use serde::{Deserialize, Serialize};
27
28use super::error::StoreError;
29use super::store::Store;
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(tag = "backend", rename_all = "snake_case")]
39pub enum SecretRef {
40 Env {
41 env_var: String,
42 },
43 SecretsManager {
44 sm_path: String,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 sm_json_path: Option<String>,
47 },
48 PlaintextDev {
49 plaintext_dev: String,
50 },
51 OAuthConfigDir {
60 dir: String,
65 },
66 Keychain {
74 service: String,
76 account: String,
78 },
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct IdentityAccount {
86 pub id: String,
87 pub name: String,
88 pub provider: String, pub kind: String, #[serde(default)]
91 pub display_name: String,
92 pub secret_ref: SecretRef,
93 #[serde(default = "default_context_json")]
96 pub context: serde_json::Value,
97 #[serde(default = "default_identity_status")]
98 pub status: String, pub created_at: i64,
100 pub updated_at: i64,
101}
102
103fn default_context_json() -> serde_json::Value {
104 serde_json::json!({})
105}
106
107fn default_identity_status() -> String {
108 "unknown".to_string()
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct AgentIdentityLink {
114 pub agent_id: String,
115 pub account_id: String,
116 pub provider: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Identity {
124 pub id: String,
125 pub name: String,
126 #[serde(default)]
127 pub description: String,
128 #[serde(default)]
129 pub is_blank: bool,
130 pub created_at: i64,
131 pub updated_at: i64,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct IdentityBinding {
137 pub identity_id: String,
138 pub provider: String,
139 pub account_id: String,
140}
141
142impl Store {
143 pub fn identity_list(
149 &self,
150 provider: Option<&str>,
151 ) -> Result<Vec<IdentityAccount>, StoreError> {
152 let conn = self.conn.lock().unwrap();
153 let mut rows_vec = Vec::new();
154 let map_row = |row: &rusqlite::Row| -> rusqlite::Result<IdentityAccount> {
155 let secret_ref_json: String = row.get(5)?;
156 let context_json: String = row.get(6)?;
157 Ok(IdentityAccount {
158 id: row.get(0)?,
159 name: row.get(1)?,
160 provider: row.get(2)?,
161 kind: row.get(3)?,
162 display_name: row.get(4)?,
163 secret_ref: serde_json::from_str(&secret_ref_json).map_err(|e| {
164 rusqlite::Error::FromSqlConversionFailure(
165 5,
166 rusqlite::types::Type::Text,
167 Box::new(e),
168 )
169 })?,
170 context: serde_json::from_str(&context_json)
171 .unwrap_or_else(|_| serde_json::json!({})),
172 status: row.get(7)?,
173 created_at: row.get(8)?,
174 updated_at: row.get(9)?,
175 })
176 };
177 match provider {
178 Some(p) => {
179 let mut stmt = conn.prepare(
180 "SELECT id, name, provider, kind, display_name, secret_ref, context,
181 status, created_at, updated_at
182 FROM db_identity_accounts
183 WHERE provider = ?1
184 ORDER BY updated_at DESC",
185 )?;
186 let iter = stmt.query_map(params![p], map_row)?;
187 for r in iter {
188 rows_vec.push(r?);
189 }
190 }
191 None => {
192 let mut stmt = conn.prepare(
193 "SELECT id, name, provider, kind, display_name, secret_ref, context,
194 status, created_at, updated_at
195 FROM db_identity_accounts
196 ORDER BY updated_at DESC",
197 )?;
198 let iter = stmt.query_map([], map_row)?;
199 for r in iter {
200 rows_vec.push(r?);
201 }
202 }
203 }
204 Ok(rows_vec)
205 }
206
207 pub fn identity_get(&self, id: &str) -> Result<Option<IdentityAccount>, StoreError> {
208 let conn = self.conn.lock().unwrap();
209 let mut stmt = conn.prepare(
210 "SELECT id, name, provider, kind, display_name, secret_ref, context,
211 status, created_at, updated_at
212 FROM db_identity_accounts WHERE id = ?1",
213 )?;
214 let result = stmt.query_row(params![id], |row| {
215 let secret_ref_json: String = row.get(5)?;
216 let context_json: String = row.get(6)?;
217 Ok(IdentityAccount {
218 id: row.get(0)?,
219 name: row.get(1)?,
220 provider: row.get(2)?,
221 kind: row.get(3)?,
222 display_name: row.get(4)?,
223 secret_ref: serde_json::from_str(&secret_ref_json).map_err(|e| {
224 rusqlite::Error::FromSqlConversionFailure(
225 5,
226 rusqlite::types::Type::Text,
227 Box::new(e),
228 )
229 })?,
230 context: serde_json::from_str(&context_json)
231 .unwrap_or_else(|_| serde_json::json!({})),
232 status: row.get(7)?,
233 created_at: row.get(8)?,
234 updated_at: row.get(9)?,
235 })
236 });
237 match result {
238 Ok(a) => Ok(Some(a)),
239 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
240 Err(e) => Err(e.into()),
241 }
242 }
243
244 pub fn identity_upsert(&self, account: &IdentityAccount) -> Result<(), StoreError> {
248 let conn = self.conn.lock().unwrap();
249 let secret_ref_json = serde_json::to_string(&account.secret_ref)?;
250 let context_json = serde_json::to_string(&account.context)?;
251 conn.execute(
252 "INSERT INTO db_identity_accounts
253 (id, name, provider, kind, display_name, secret_ref, context,
254 status, created_at, updated_at)
255 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
256 ON CONFLICT(id) DO UPDATE SET
257 name = excluded.name,
258 provider = excluded.provider,
259 kind = excluded.kind,
260 display_name = excluded.display_name,
261 secret_ref = excluded.secret_ref,
262 context = excluded.context,
263 status = excluded.status,
264 updated_at = excluded.updated_at",
265 params![
266 account.id,
267 account.name,
268 account.provider,
269 account.kind,
270 account.display_name,
271 secret_ref_json,
272 context_json,
273 account.status,
274 account.created_at,
275 account.updated_at,
276 ],
277 )?;
278 Ok(())
279 }
280
281 pub fn identity_delete(&self, id: &str) -> Result<bool, StoreError> {
282 let conn = self.conn.lock().unwrap();
283 let rows = conn.execute("DELETE FROM db_identity_accounts WHERE id = ?1", params![id])?;
284 Ok(rows > 0)
285 }
286
287 pub fn agent_identity_link(
293 &self,
294 agent_id: &str,
295 account_id: &str,
296 provider: &str,
297 ) -> Result<(), StoreError> {
298 let conn = self.conn.lock().unwrap();
299 conn.execute(
300 "INSERT INTO db_agent_identity_links (agent_id, account_id, provider)
301 VALUES (?1, ?2, ?3)
302 ON CONFLICT(agent_id, provider) DO UPDATE SET account_id = excluded.account_id",
303 params![agent_id, account_id, provider],
304 )?;
305 Ok(())
306 }
307
308 pub fn agent_identity_unlink(
311 &self,
312 agent_id: &str,
313 provider: &str,
314 ) -> Result<bool, StoreError> {
315 let conn = self.conn.lock().unwrap();
316 let rows = conn.execute(
317 "DELETE FROM db_agent_identity_links WHERE agent_id = ?1 AND provider = ?2",
318 params![agent_id, provider],
319 )?;
320 Ok(rows > 0)
321 }
322
323 pub fn agent_identity_list_for_agent(
325 &self,
326 agent_id: &str,
327 ) -> Result<Vec<AgentIdentityLink>, StoreError> {
328 let conn = self.conn.lock().unwrap();
329 let mut stmt = conn.prepare(
330 "SELECT agent_id, account_id, provider
331 FROM db_agent_identity_links
332 WHERE agent_id = ?1
333 ORDER BY provider",
334 )?;
335 let iter = stmt.query_map(params![agent_id], |row| {
336 Ok(AgentIdentityLink {
337 agent_id: row.get(0)?,
338 account_id: row.get(1)?,
339 provider: row.get(2)?,
340 })
341 })?;
342 let mut out = Vec::new();
343 for r in iter {
344 out.push(r?);
345 }
346 Ok(out)
347 }
348
349 pub fn bundle_identity_list(&self) -> Result<Vec<Identity>, StoreError> {
354 let conn = self.conn.lock().unwrap();
355 let mut stmt = conn.prepare(
356 "SELECT id, name, description, is_blank, created_at, updated_at
357 FROM db_identity_bundles
358 ORDER BY is_blank ASC, updated_at DESC",
359 )?;
360 let iter = stmt.query_map([], |row| {
361 Ok(Identity {
362 id: row.get(0)?,
363 name: row.get(1)?,
364 description: row.get(2)?,
365 is_blank: row.get::<_, i64>(3)? != 0,
366 created_at: row.get(4)?,
367 updated_at: row.get(5)?,
368 })
369 })?;
370 let mut out = Vec::new();
371 for r in iter {
372 out.push(r?);
373 }
374 Ok(out)
375 }
376
377 pub fn bundle_identity_get(&self, id: &str) -> Result<Option<Identity>, StoreError> {
378 let conn = self.conn.lock().unwrap();
379 let mut stmt = conn.prepare(
380 "SELECT id, name, description, is_blank, created_at, updated_at
381 FROM db_identity_bundles WHERE id = ?1",
382 )?;
383 let result = stmt.query_row(params![id], |row| {
384 Ok(Identity {
385 id: row.get(0)?,
386 name: row.get(1)?,
387 description: row.get(2)?,
388 is_blank: row.get::<_, i64>(3)? != 0,
389 created_at: row.get(4)?,
390 updated_at: row.get(5)?,
391 })
392 });
393 match result {
394 Ok(i) => Ok(Some(i)),
395 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
396 Err(e) => Err(e.into()),
397 }
398 }
399
400 pub fn bundle_identity_upsert(&self, identity: &Identity) -> Result<(), StoreError> {
404 let conn = self.conn.lock().unwrap();
405 conn.execute(
406 "INSERT INTO db_identity_bundles
407 (id, name, description, is_blank, created_at, updated_at)
408 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
409 ON CONFLICT(id) DO UPDATE SET
410 name = excluded.name,
411 description = excluded.description,
412 updated_at = excluded.updated_at",
413 params![
414 identity.id,
415 identity.name,
416 identity.description,
417 identity.is_blank as i64,
418 identity.created_at,
419 identity.updated_at,
420 ],
421 )?;
422 Ok(())
423 }
424
425 pub fn bundle_identity_delete(&self, id: &str) -> Result<bool, StoreError> {
428 if id == "blank" {
429 return Err(StoreError::Other(
430 "cannot delete the blank Identity singleton".to_string(),
431 ));
432 }
433 let conn = self.conn.lock().unwrap();
434 let rows = conn.execute("DELETE FROM db_identity_bundles WHERE id = ?1", params![id])?;
435 Ok(rows > 0)
436 }
437
438 pub fn bundle_identity_bind(
443 &self,
444 identity_id: &str,
445 provider: &str,
446 account_id: &str,
447 ) -> Result<(), StoreError> {
448 let conn = self.conn.lock().unwrap();
449 conn.execute(
450 "INSERT INTO db_identity_bindings (identity_id, provider, account_id)
451 VALUES (?1, ?2, ?3)
452 ON CONFLICT(identity_id, provider) DO UPDATE SET account_id = excluded.account_id",
453 params![identity_id, provider, account_id],
454 )?;
455 Ok(())
456 }
457
458 pub fn bundle_identity_unbind(
461 &self,
462 identity_id: &str,
463 provider: &str,
464 ) -> Result<bool, StoreError> {
465 let conn = self.conn.lock().unwrap();
466 let rows = conn.execute(
467 "DELETE FROM db_identity_bindings WHERE identity_id = ?1 AND provider = ?2",
468 params![identity_id, provider],
469 )?;
470 Ok(rows > 0)
471 }
472
473 pub fn bundle_identity_bindings(
475 &self,
476 identity_id: &str,
477 ) -> Result<Vec<IdentityBinding>, StoreError> {
478 let conn = self.conn.lock().unwrap();
479 let mut stmt = conn.prepare(
480 "SELECT identity_id, provider, account_id
481 FROM db_identity_bindings
482 WHERE identity_id = ?1
483 ORDER BY provider ASC",
484 )?;
485 let iter = stmt.query_map(params![identity_id], |row| {
486 Ok(IdentityBinding {
487 identity_id: row.get(0)?,
488 provider: row.get(1)?,
489 account_id: row.get(2)?,
490 })
491 })?;
492 let mut out = Vec::new();
493 for r in iter {
494 out.push(r?);
495 }
496 Ok(out)
497 }
498}